﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Xml;
using SharpObjects.Configuration;
using SharpObjects.Configuration.ConfigurationProviders;
using SharpObjects.Interfaces;
using SharpObjects.RuleEngine;

namespace SharpObjects.RuleEngine.RuleElements
{




    public class FieldElement : IXmlInitializable
    {
        public eFieldType FieldType { get; set; }
        public eDataStore FieldDataStore { get; set; }
        public string FieldName { get; set; }

        #region IInitializable Members

        public void Initialize(XmlElement initializationElement)
        {
            XmlConfigurationProvider init = new XmlConfigurationProvider(initializationElement);

            this.FieldType = init.GetEnum<eFieldType>("@data_type", eFieldType.String);
            this.FieldDataStore = init.GetEnum<eDataStore>("@data_store");
            this.FieldName = init.GetString("self::*");

            this.IsInitialized = true;
        }

        public bool IsInitialized { get; set; }

        #endregion

        public object GetValue(IRuleEngineContext ruleEngineContext, IDataProvider dataProvider)
        {
            object value;

            if (FieldDataStore == eDataStore.Input_Data)
            {
                switch (FieldType)
                {
                    case eFieldType.String:
                        value = dataProvider.GetString(FieldName);
                        break;
                    case eFieldType.Int:
                        value = dataProvider.GetInteger(FieldName);
                        break;
                    case eFieldType.Double:
                        value = dataProvider.GetDouble(FieldName);
                        break;
                    case eFieldType.DateTime:
                        value = dataProvider.GetDateTime(FieldName);
                        break;
                    case eFieldType.Bool:
                        value = dataProvider.GetBoolean(FieldName);
                        break;
                    default:
                        throw new ArgumentException("Unknown eFieldType enum value: " + FieldType);
                }
            }
            else if (FieldDataStore == eDataStore.Context)
            {
                switch (FieldType)
                {
                    case eFieldType.String:
                        value = ruleEngineContext.GetValue<string>(FieldName, string.Empty);
                        break;
                    case eFieldType.Int:
                        value = ruleEngineContext.GetValue<int>(FieldName);
                        break;
                    case eFieldType.Double:
                        value = ruleEngineContext.GetValue<double>(FieldName);
                        break;
                    case eFieldType.DateTime:
                        value = ruleEngineContext.GetValue<DateTime>(FieldName);
                        break;
                    case eFieldType.Bool:
                        value = ruleEngineContext.GetValue<bool>(FieldName);
                        break;
                    default:
                        throw new ArgumentException("Unknown eFieldType enum value: " + FieldType);
                }
            }
            else
            {
                throw new ArgumentException("Unknown eDataStore enum value: " + FieldDataStore);
            }

            return value;
        }

        public bool IsLessThan(FieldElement threshold, IRuleEngineContext ruleEngineContext, IDataProvider dataProvider)
        {
            if (this.FieldType != threshold.FieldType)
            {
                throw new ArgumentException(string.Format("Data types of this '{0}' and threshold '{1}' don't match", this.FieldName, threshold.FieldName));
            }

            if (this.FieldType == eFieldType.Bool || this.FieldType == eFieldType.String)
            {
                throw new ArgumentException(string.Format("Cannot apply operator < to arguments of type '{0}'", this.FieldType.ToString()));
            }

            switch (this.FieldType)
            {
                case eFieldType.DateTime:
                    return ((DateTime)this.GetValue(ruleEngineContext, dataProvider)) < ((DateTime)threshold.GetValue(ruleEngineContext, dataProvider));
                case eFieldType.Double:
                    return ((double)this.GetValue(ruleEngineContext, dataProvider)) < ((double)threshold.GetValue(ruleEngineContext, dataProvider));
                case eFieldType.Int:
                    return ((int)this.GetValue(ruleEngineContext, dataProvider)) < ((int)threshold.GetValue(ruleEngineContext, dataProvider));
                default:
                    throw new ArgumentException(string.Format("Unknown data type: {0}", this.FieldType));
            }
        }

        public bool IsGreaterThan(FieldElement threshold, IRuleEngineContext ruleEngineContext, IDataProvider dataProvider)
        {
            if (this.FieldType != threshold.FieldType)
            {
                throw new ArgumentException(string.Format("Data types of this '{0}' and threshold '{1}' don't match", this.FieldName, threshold.FieldName));
            }

            if (this.FieldType == eFieldType.Bool || this.FieldType == eFieldType.String)
            {
                throw new ArgumentException(string.Format("Cannot apply operator > to arguments of type '{0}'", this.FieldType.ToString()));
            }

            switch (this.FieldType)
            {
                case eFieldType.DateTime:
                    return ((DateTime)this.GetValue(ruleEngineContext, dataProvider)) > ((DateTime)threshold.GetValue(ruleEngineContext, dataProvider));
                case eFieldType.Double:
                    return ((double)this.GetValue(ruleEngineContext, dataProvider)) > ((double)threshold.GetValue(ruleEngineContext, dataProvider));
                case eFieldType.Int:
                    return ((int)this.GetValue(ruleEngineContext, dataProvider)) > ((int)threshold.GetValue(ruleEngineContext, dataProvider));
                default:
                    throw new ArgumentException(string.Format("Unknown data type: {0}", this.FieldType));
            }
        }

        public bool IsBetween(FieldElement lowThreshold, FieldElement highThreshold, IRuleEngineContext ruleEngineContext, IDataProvider dataProvider)
        {
            if (this.FieldType != lowThreshold.FieldType)
            {
                throw new ArgumentException(string.Format("Data types of this '{0}' and threshold '{1}' don't match", this.FieldName, lowThreshold.FieldName));
            }

            if (this.FieldType != highThreshold.FieldType)
            {
                throw new ArgumentException(string.Format("Data types of this '{0}' and threshold '{1}' don't match", this.FieldName, highThreshold.FieldName));
            }

            if (this.FieldType == eFieldType.Bool || this.FieldType == eFieldType.String)
            {
                throw new ArgumentException(string.Format("Cannot apply operators < and > to arguments of type '{0}'", this.FieldType.ToString()));
            }

            switch (this.FieldType)
            {
                case eFieldType.DateTime:
                    return (((DateTime)this.GetValue(ruleEngineContext, dataProvider)) >=
                            ((DateTime)lowThreshold.GetValue(ruleEngineContext, dataProvider)))
                           &&
                           (((DateTime)this.GetValue(ruleEngineContext, dataProvider)) <=
                            ((DateTime)highThreshold.GetValue(ruleEngineContext, dataProvider)));

                case eFieldType.Double:
                    return (((double)this.GetValue(ruleEngineContext, dataProvider)) >=
                            ((double)lowThreshold.GetValue(ruleEngineContext, dataProvider)))
                           &&
                           (((double)this.GetValue(ruleEngineContext, dataProvider)) <=
                            ((double)highThreshold.GetValue(ruleEngineContext, dataProvider)));
                case eFieldType.Int:
                    return (((int)this.GetValue(ruleEngineContext, dataProvider)) >=
                            ((int)lowThreshold.GetValue(ruleEngineContext, dataProvider)))
                           &&
                           (((int)this.GetValue(ruleEngineContext, dataProvider)) <=
                            ((int)highThreshold.GetValue(ruleEngineContext, dataProvider)));
                default:
                    throw new ArgumentException(string.Format("Unknown data type: {0}", this.FieldType));
            }
        }
    }
}
